import { cls, Component, StyledProps } from '@knno/jsx';
import { defineElement } from '../defineElement';
import { LINE_HEIGHT, PADDING, textareaStyle } from './textarea.css';

const TextAreaElement = defineElement('knno-textarea', HTMLTextAreaElement, 'textarea');

function doResize(area: HTMLTextAreaElement) {
	if (!area.isConnected) return;
	area.style.height = '';
	const style = window.getComputedStyle(area);
	const height = area.scrollHeight;
	area.style.height = height + 'px';
	const maxHeight = parseFloat(style.maxHeight);
	if (height > maxHeight + 1) {
		area.style.overflow = 'auto';
	} else {
		area.style.overflow = '';
	}
}

type ResizeType = 'fixed' | 'auto' | 'manual';

type TextareaProps = StyledProps<Textarea> & {
	id?: string;
	name?: string;
	autofocus?: boolean;
	placeholder?: string;
	disabled?: boolean;
	readOnly?: boolean;
	maxLength?: number;
	lines?: number;
	maxLines?: number;
	resizeType?: ResizeType;
	value?: string;
	onChange?: (this: Textarea, e: Event) => void;
	onInput?: (this: Textarea, e: Event) => void;
	onBlur?: (this: Textarea, e: Event) => void;
	onFocus?: (this: Textarea, e: Event) => void;
	onClick?: (this: Textarea, e: MouseEvent) => void;
	onKeyDown?: (this: Textarea, e: KeyboardEvent) => void;
	onKeyUp?: (this: Textarea, e: KeyboardEvent) => void;
	onKeyPress?: (this: Textarea, e: KeyboardEvent) => void;
	onPointerDown?: (this: Textarea, e: PointerEvent) => void;
	onPointerUp?: (this: Textarea, e: PointerEvent) => void;
	onPointerCancel?: (this: Textarea, e: PointerEvent) => void;
	onPointerMove?: (this: Textarea, e: PointerEvent) => void;
};

function setLines(element: HTMLTextAreaElement, len: number | null) {
	if (typeof len === 'number' && len > 0) {
		element.style.minHeight = `calc(${len * LINE_HEIGHT}rem + ${PADDING * 2}em)`;
	} else {
		element.style.minHeight = '';
	}
}

function setMaxLines(element: HTMLTextAreaElement, len: number | null) {
	if (typeof len === 'number' && len > 0) {
		element.style.maxHeight = `calc(${len * LINE_HEIGHT}rem + ${PADDING * 2}em)`;
	} else {
		element.style.maxHeight = '';
	}
}

export class Textarea extends Component<TextareaProps, Textarea, HTMLTextAreaElement> {
	protected render(): Node {
		let resizeType = this.props.resizeType ?? 'auto';
		if (!/^(auto|fixed|manual)$/.test(resizeType)) {
			resizeType = 'auto';
		}
		const textarea = (
			<TextAreaElement
				id={this.props.id}
				name={this.props.name}
				autofocus={this.props.autofocus}
				class={cls(this.props.class, textareaStyle)}
				style={this.props.style}
				maxlength={this.props.maxLength ?? -1}
				placeholder={this.props.placeholder ?? ''}
				disabled={this.props.disabled}
				readOnly={this.props.readOnly}
				resize={resizeType ?? 'auto'}
				value={this.props.value ?? ''}
				onInput={(evt) => {
					doResize(this.el);
					(this as Textarea).call('onInput', evt);
				}}
				onConnected={() => {
					doResize(this.el);
				}}
				onChange={(evt) => (this as Textarea).call('onChange', evt)}
				onBlur={(evt) => (this as Textarea).call('onBlur', evt)}
				onFocus={(evt) => (this as Textarea).call('onFocus', evt)}
				onClick={(evt) => (this as Textarea).call('onClick', evt)}
				onKeyDown={(evt) => (this as Textarea).call('onKeyDown', evt)}
				onKeyUp={(evt) => (this as Textarea).call('onKeyUp', evt)}
				onKeyPress={(evt) => (this as Textarea).call('onKeyPress', evt)}
				onPointerDown={(evt) => (this as Textarea).call('onPointerDown', evt)}
				onPointerUp={(evt) => (this as Textarea).call('onPointerUp', evt)}
				onPointerCancel={(evt) => (this as Textarea).call('onPointerCancel', evt)}
				onPointerMove={(evt) => (this as Textarea).call('onPointerMove', evt)}
			/>
		) as HTMLTextAreaElement;
		if (this.props.lines) {
			setLines(textarea, this.props.lines);
		}
		if (this.props.maxLines) {
			setMaxLines(textarea, this.props.maxLines);
		}
		return textarea;
	}

	get placeholder(): string {
		return this.el.placeholder;
	}

	setPlaceholder(text: string): this {
		this.el.placeholder = text;
		return this;
	}

	get disabled(): boolean {
		return this.el.disabled;
	}
	setDisabled(value: boolean): this {
		this.el.disabled = value;
		return this;
	}

	get readOnly(): boolean {
		return this.el.readOnly;
	}
	setReadonly(value: boolean): this {
		this.el.readOnly = value;
		return this;
	}

	get maxLength(): number {
		return this.el.maxLength;
	}
	/**
	 * Set max length of textarea.
	 * @param len -1 means no limit
	 */
	setMaxLength(len: number): this {
		if (len < 0) {
			this.el.setAttribute('maxlength', '-1');
		} else {
			this.el.maxLength = len;
		}
		return this;
	}

	setLines(len: number | null): this {
		setLines(this.el, len);
		return this;
	}

	setMaxLines(len: number | null): this {
		setMaxLines(this.el, len);
		return this;
	}

	get resizeType(): ResizeType {
		return (this.el.getAttribute('resize') as ResizeType) ?? 'auto';
	}
	setResizeType(value: ResizeType): this {
		if (!/^(auto|fixed|manual)$/.test(value)) {
			value = 'auto';
		}
		this.el.setAttribute('resize', value);
		doResize(this.el);
		return this;
	}

	get value(): string {
		return this.el.value;
	}
	setValue(value: string): this {
		this.el.value = value;
		doResize(this.el);
		return this;
	}

	focus(): this {
		this.el.focus();
		return this;
	}

	blur(): this {
		this.el.blur();
		return this;
	}

	select(): this {
		this.el.select();
		return this;
	}

	setSelectionRange(start: number, end: number, direction?: 'forward' | 'backward' | 'none'): this {
		this.el.setSelectionRange(start, end, direction);
		return this;
	}

	onChange(callback: ((this: Textarea, e: Event) => void) | undefined) {
		this.props.onChange = callback;
	}
	onInput(callback: ((this: Textarea, e: Event) => void) | undefined) {
		this.props.onInput = callback;
	}
	onBlur(callback: ((this: Textarea, e: Event) => void) | undefined) {
		this.props.onBlur = callback;
	}
	onFocus(callback: ((this: Textarea, e: Event) => void) | undefined) {
		this.props.onFocus = callback;
	}
	onClick(callback: ((this: Textarea, e: MouseEvent) => void) | undefined) {
		this.props.onClick = callback;
	}
	onKeyDown(callback: ((this: Textarea, e: KeyboardEvent) => void) | undefined) {
		this.props.onKeyDown = callback;
	}
	onKeyUp(callback: ((this: Textarea, e: KeyboardEvent) => void) | undefined) {
		this.props.onKeyUp = callback;
	}
	onKeyPress(callback: ((this: Textarea, e: KeyboardEvent) => void) | undefined) {
		this.props.onKeyPress = callback;
	}
	onPointerDown(callback: ((this: Textarea, e: PointerEvent) => void) | undefined) {
		this.props.onPointerDown = callback;
	}
	onPointerUp(callback: ((this: Textarea, e: PointerEvent) => void) | undefined) {
		this.props.onPointerUp = callback;
	}
	onPointerCancel(callback: ((this: Textarea, e: PointerEvent) => void) | undefined) {
		this.props.onPointerCancel = callback;
	}
	onPointerMove(callback: ((this: Textarea, e: PointerEvent) => void) | undefined) {
		this.props.onPointerMove = callback;
	}
}
